package pers.sen.health.security;

import com.alibaba.fastjson.JSONObject;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import pers.sen.health.entity.Result;
import pers.sen.health.jwt.JwtUtils;
import pers.sen.health.jwt.UserInfo;
import pers.sen.health.po.User;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.PrivateKey;
import java.util.Collection;

/**
 * 对请求拦截做登录认证，认证成功后生成token，并将token返回给客户端
 * 该类继承自UsernamePasswordAuthenticationFilter，重写了其中的2个方法
 * attemptAuthentication：接收JSON格式的用户信息，并解析用户凭证。
 * successfulAuthentication：用户成功登录后，这个方法会被调用，我们在这个方法里生成token并返回。
 */
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    private JWTProperties properties;


    public JWTAuthenticationFilter(AuthenticationManager authenticationManager, JWTProperties properties) {
        this.properties = properties;
        setAuthenticationManager(authenticationManager);
        this.authenticationManager = authenticationManager;
        // 登录表单的请求地址
        super.setFilterProcessesUrl("/login");
        System.out.println("filter====" + authenticationManager + "====");
    }


    /**
     * 做认证准备的方法attemptAuthentication：
     * 1. 接收表单的JSON 数据，解析存储到Authentication中
     * Authentication authentication= new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword());
     * 2. 提交做登录认证（通过UserDetailsService查询数据库验证）
     * authenticationManager.authenticate(authentication);
     *
     * @param request  前端提交的请求登录表单JSON
     * @param response 响应数据
     * @return 提交需要认证的用户信息（用户名/密码）
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response)
            throws AuthenticationException {
        try {
            // 前端 发送 json 对象  username  password
            JSONObject jsonObject = new JSONObject();
            // 从输入流中获取到登录的信息，并将流中的JSON对象解析成Java对象【fastjson】
            User loginUser = jsonObject.parseObject(request.getInputStream(), User.class);
            System.out.println("解析前端登录的信息：user = " + loginUser);
            // 将用户名、密码存储到Authentication中
            Authentication authentication =
                    new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword());
            // 发送用户名、密码做登录校验，如果校验不通过会产生异常
            return authenticationManager.authenticate(authentication);
        } catch (Exception e) {
            // 捕获到异常，认证不通过
            e.printStackTrace();
            try {
                // 返回结果
                Result info = new Result();
                info.setFlag(false);
                info.setStatus(500);
                info.setMessage("账号或密码认证失败");
                // 响应格式
                response.setContentType("text/json;charset=utf-8");
                // 转成JSON格式
                String s = JSONObject.toJSONString(info);
                // 响应给前端
                response.getWriter().print(s);
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            return null;
        }
    }


    /**
     * 认证成功后执行的方法successfulAuthentication：
     * 1. 如果验证成功，将用户信息（用户名、权限【前端无需要密码】）就生成token并返回
     *
     * @param request    请求
     * @param response   响应
     * @param chain      过滤器链
     * @param authResult 验证结果
     */
    @Override
    public void successfulAuthentication(HttpServletRequest request,
                                         HttpServletResponse response,
                                         FilterChain chain,
                                         Authentication authResult) throws IOException, ServletException {
        // 从验证结果中获取所有用户信息，UserDetailsService实现类返回的验证结果：return new User(username,password,authorities);
        org.springframework.security.core.userdetails.User jwtUser = (org.springframework.security.core.userdetails.User) authResult.getPrincipal();
        // 获取认证后数据库获取的该用户权限关键字集合
        Collection<GrantedAuthority> authorities = jwtUser.getAuthorities();
        // 转成数组
        GrantedAuthority[] objects = authorities.toArray(new GrantedAuthority[]{});
        // 拼接字符串，存储到UserInfo中，转成字符串，加密token
        StringBuffer sb = new StringBuffer();
        // 遍历权限数组拼接字符串
        for (int i = 0; i < authorities.size(); i++) {
            String authority = objects[i].getAuthority();
            if (i == authorities.size() - 1) {
                // 最后一个
                sb.append(authority);
            } else {
                // 使用“-”拼接（ROLE_ADMIN-FINDALL...）
                sb.append(authority + "-");
            }
        }
        // 开始对用户进行 加密  生成token
        UserInfo userInfo = new UserInfo();
        // 权限字符串
        userInfo.setRoles(sb.toString());
        // 用户名
        userInfo.setUsername(jwtUser.getUsername());

        // 获取私钥（需要配置JWTProperties中的属性）
        PrivateKey privateKey = properties.getPrivateKey();

        String token = null;
        // 设置响应格式json
        response.setContentType("application/json; charset=utf-8");
        Result tokenResult = new Result();
        try {
            // 使用私钥加密token，生成加密后的token字符串，expireMinutes：失效时间properties.getExpire()【配置文件配置】
            token = JwtUtils.generateToken(userInfo, privateKey, properties.getExpire());
            // 给加密的token增加前缀“PREFIX”
            String tokenStr = JwtUtils.TOKEN_PREFIX + token;
            // 设置响应头“Authentication”，以响应头传递token，前端可以获取响应头的token存储，
            response.setHeader(JwtUtils.TOKEN_NAME, tokenStr);
            // 响应结果
            tokenResult.setFlag(true);
            tokenResult.setStatus(200);
            // 将token放在message中，前端也可以在message中获取
            tokenResult.setMessage(tokenStr);
        } catch (Exception e) {
            // token生成失败
            e.printStackTrace();
            tokenResult.setFlag(false);
            tokenResult.setStatus(500);
            tokenResult.setMessage("token生成失败");
        }
        // 将token信息响应给前端
        String s = JSONObject.toJSONString(tokenResult);
        response.getWriter().print(s);
    }

    /**
     * 认证不成功执行的方法
     *
     * @param request  请求
     * @param response 响应
     * @param failed   错误信息
     */
    @Override
    public void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        // 响应状态码
        response.setStatus(403);
        response.setContentType("application/json; charset=utf-8");
        if (failed instanceof BadCredentialsException) {
            response.getWriter().write("authentication failed, reason: 密码错误");
        } else {
            response.getWriter().write("authentication failed, reason: " + failed.getMessage());
        }
    }
}

